/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023.
 * Description: support multi-threads unpack initramfs
 * Author: yangzhuohao <yangzhuohao1@huawei.com>
 * Create: 2023-03-07
 */

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/delay.h>
#include <linux/string.h>
#include <linux/dirent.h>
#include <linux/syscalls.h>
#include <linux/utime.h>
#include <linux/file.h>
#include <linux/memblock.h>
#include <linux/namei.h>
#ifdef CONFIG_IMA_DIGEST_LIST
#include <linux/xattr.h>
#include <linux/initramfs.h>
#endif
#include <linux/init_syscalls.h>
#include "multi_threads_initramfs.h"

struct cpio_header_info {
	unsigned long ino;
	umode_t mode;
	uid_t uid;
	gid_t gid;
	unsigned long nlink;
	time64_t mtime;
	unsigned long body_len;
	unsigned long major;
	unsigned long minor;
	unsigned int rdev;
	unsigned long name_len;
};

#ifdef CONFIG_IMA_DIGEST_LIST
struct metadata {
	int is_metadata;
	unsigned long metadata_len;
	unsigned long max_len;
	char *metadata_buf;
	char *metadata_buf_ptr;
};
#endif

struct unpack_desc {
	char *victim;
	char *collect;
	char *collected;
	char *message;
	char *header_buf;
	char *symlink_buf;
	char *name_buf;
#ifdef CONFIG_IMA_DIGEST_LIST
	char *previous_name_buf;
#endif
	unsigned long byte_count;
	long remains;
	struct file *wfile;
	loff_t wfile_pos;
	loff_t this_header;
	loff_t next_header;
	struct cpio_header_info cpio_header;
	struct hash *head[32];
	struct list_head dir_list;
#ifdef CONFIG_IMA_DIGEST_LIST
	struct metadata metadata;
#endif
	enum state state;
	enum state next_state;
	char msg_buf[64];
	struct completion rootfs_running;
};

static RADIX_TREE(unpack_desc_tree, GFP_KERNEL);
static void __init pid_insert_desc(int pid, struct unpack_desc *desc)
{
	radix_tree_insert(&unpack_desc_tree, (unsigned long)pid, desc);
}

static struct unpack_desc __init *pid_to_desc(int pid)
{
	return radix_tree_lookup(&unpack_desc_tree, (unsigned long)pid);
}

static void __init delete_pid_desc(int pid)
{
	radix_tree_delete(&unpack_desc_tree, (unsigned long)pid);
}

static void __init error(char *x)
{
	struct unpack_desc *unpack_desc = pid_to_desc(current->pid);
	if (!unpack_desc->message)
		unpack_desc->message = x;
}

static char __init *find_link(struct hash **head, struct cpio_header_info *cpio_header, char *name)
{
	struct hash **p, *q;
	unsigned long major = cpio_header->major;
	unsigned long minor = cpio_header->minor;
	unsigned long ino = cpio_header->ino;
	umode_t mode = cpio_header->mode;

	for (p = head + hash(major, minor, ino); *p; p = &(*p)->next) {
		if ((*p)->ino != ino)
			continue;
		if ((*p)->minor != minor)
			continue;
		if ((*p)->major != major)
			continue;
		if (((*p)->mode ^ mode) & S_IFMT)
			continue;
		return (*p)->name;
	}
	q = kmalloc(sizeof(struct hash), GFP_KERNEL | __GFP_NOWARN);
	if (!q)
		panic("can't allocate link hash entry");
	q->major = major;
	q->minor = minor;
	q->ino = ino;
	q->mode = mode;
	strcpy(q->name, name);
	q->next = NULL;
	*p = q;
	return NULL;
}

static void __init free_hash(struct hash **head)
{
	struct hash **p, *q;

	for (p = head; p < head + 32; p++) {
		while (*p) {
			q = *p;
			*p = q->next;
			kfree(q);
		}
	}
}

static void __init dir_add(struct list_head *dir_list, const char *name, time64_t mtime)
{
	struct dir_entry *de = kmalloc(sizeof(struct dir_entry), GFP_KERNEL | __GFP_NOWARN);

	if (!de)
		panic("can't allocate dir_entry buffer");
	INIT_LIST_HEAD(&de->list);
	de->name = kstrdup(name, GFP_KERNEL);
	de->mtime = mtime;
	list_add(&de->list, dir_list);
}

static void __init dir_utime(struct list_head *dir_list)
{
	struct dir_entry *de, *tmp;

	list_for_each_entry_safe(de, tmp, dir_list, list) {
		list_del(&de->list);
		do_utime(de->name, de->mtime);
		kfree(de->name);
		kfree(de);
	}
}

/* cpio header parsing */

static int __init parse_header(char *s, struct cpio_header_info *cpio_header)
{
	unsigned long parsed[12];
	char buf[9];
	int i;
	int ret;

	buf[8] = '\0';
	for (i = 0, s += 6; i < 12; i++, s += 8) {
		memcpy(buf, s, 8);
		ret = kstrtoul(buf, 16, &parsed[i]);
		if (ret != 0)
			return ret;
	}
	cpio_header->ino = parsed[0];
	cpio_header->mode = (umode_t)parsed[1];
	cpio_header->uid = (uid_t)parsed[2];
	cpio_header->gid = (gid_t)parsed[3];
	cpio_header->nlink = parsed[4];
	cpio_header->mtime = (time64_t)parsed[5]; /* breaks in y2106 */
	cpio_header->body_len = parsed[6];
	cpio_header->major = parsed[7];
	cpio_header->minor = parsed[8];
	cpio_header->rdev = new_encode_dev((dev_t)MKDEV(parsed[9], parsed[10]));
	cpio_header->name_len = parsed[11];
	return 0;
}

static inline void __init eat(struct unpack_desc *unpack_desc, unsigned long n)
{
	unpack_desc->victim += n;
	unpack_desc->this_header += n;
	unpack_desc->byte_count -= n;
}


static void __init read_into(struct unpack_desc *unpack_desc, char *buf, unsigned long size, enum state next)
{
	if (unpack_desc->byte_count >= size) {
		unpack_desc->collected = unpack_desc->victim;
		eat(unpack_desc, size);
		unpack_desc->state = next;
	} else {
		unpack_desc->collect = unpack_desc->collected = buf;
		unpack_desc->remains = size;
		unpack_desc->next_state = next;
		unpack_desc->state = Collect;
	}
}

static int __init do_start(struct unpack_desc *unpack_desc)
{
	read_into(unpack_desc, unpack_desc->header_buf, 110, GotHeader);
	return 0;
}

static int __init do_collect(struct unpack_desc *unpack_desc)
{
	unsigned long n = (unsigned long)unpack_desc->remains;

	if (unpack_desc->byte_count < n)
		n = unpack_desc->byte_count;
	memcpy(unpack_desc->collect, unpack_desc->victim, n);
	eat(unpack_desc, n);
	unpack_desc->collect += n;
	unpack_desc->remains -= n;
	if (unpack_desc->remains != 0)
		return 1;
	unpack_desc->state = unpack_desc->next_state;
	return 0;
}

static int __init do_header(struct unpack_desc *unpack_desc)
{
	unsigned long name_len;
	unsigned long body_len;
	unsigned long mode;
	int ret;

	if (memcmp(unpack_desc->collected, "070707", 6) == 0) {
		error("incorrect cpio method used: use -H newc option");
		return 1;
	}
	if (memcmp(unpack_desc->collected, "070701", 6)) {
		error("no cpio magic");
		return 1;
	}
	ret = parse_header(unpack_desc->collected, &unpack_desc->cpio_header);
	if (ret != 0) {
		error("cpio header is corrupt");
		return 1;
	}
	name_len = unpack_desc->cpio_header.name_len;
	body_len = unpack_desc->cpio_header.body_len;
	mode = unpack_desc->cpio_header.mode;
	unpack_desc->next_header = unpack_desc->this_header + N_ALIGN(name_len) + body_len;
	unpack_desc->next_header = (unpack_desc->next_header + 3) & ~3;
	unpack_desc->state = SkipIt;
	if (name_len == 0 || name_len > PATH_MAX)
		return 0;
	if (S_ISLNK(mode)) {
		if (body_len > PATH_MAX)
			return 0;
		unpack_desc->collect = unpack_desc->collected = unpack_desc->symlink_buf;
		unpack_desc->remains = N_ALIGN(name_len) + body_len;
		unpack_desc->next_state = GotSymlink;
		unpack_desc->state = Collect;
		return 0;
	}
	if (S_ISREG(mode) || !body_len)
		read_into(unpack_desc, unpack_desc->name_buf, N_ALIGN(name_len), GotName);
	return 0;
}

static int __init do_skip(struct unpack_desc *unpack_desc)
{
	if (unpack_desc->this_header + unpack_desc->byte_count < unpack_desc->next_header) {
		eat(unpack_desc, unpack_desc->byte_count);
		return 1;
	}

	eat(unpack_desc, unpack_desc->next_header - unpack_desc->this_header);
	unpack_desc->state = unpack_desc->next_state;
	return 0;
}

static int __init do_reset(struct unpack_desc *unpack_desc)
{
	while (unpack_desc->byte_count && *unpack_desc->victim == '\0')
		eat(unpack_desc, 1);
	if (unpack_desc->byte_count && (unpack_desc->this_header & 3))
		error("broken padding");
	return 1;
}

static int __init maybe_link(struct hash **head, struct cpio_header_info *cpio_header, char *name)
{
	if (cpio_header->nlink >= 2) {
		char *old = find_link(head, cpio_header, name);

		if (old) {
			clean_path(name, 0);
			return (init_link(old, name) < 0) ? -1 : 1;
		}
	}
	return 0;
}

#ifdef CONFIG_IMA_DIGEST_LIST
static int __init __maybe_unused do_parse_metadata(char *pathname, char *metadata_buf, unsigned long metadata_len)
{
	char *buf = metadata_buf;
	char *bufend = metadata_buf + metadata_len;
	struct metadata_hdr *hdr;
	char str[sizeof(hdr->c_size) + 1];
	uint32_t entry_size;

	if (!metadata_len)
		return 0;

	str[sizeof(hdr->c_size)] = 0;

	while (buf < bufend) {
		int ret;

		if (buf + sizeof(*hdr) > bufend) {
			error("malformed metadata");
			break;
		}

		hdr = (struct metadata_hdr *)buf;
		if (hdr->c_version != 1) {
			pr_debug("Unsupported header version\n");
			break;
		}

		memcpy(str, hdr->c_size, sizeof(hdr->c_size));
		ret = kstrtou32(str, 16, &entry_size);
		if (ret || buf + entry_size > bufend ||
		    entry_size < sizeof(*hdr)) {
			error("malformed xattrs");
			break;
		}

		switch (hdr->c_type) {
		case TYPE_XATTR:
			do_setxattrs(pathname, buf + sizeof(*hdr),
				     entry_size - sizeof(*hdr));
			break;
		default:
			pr_debug("Unsupported metadata type\n");
			break;
		}

		buf += entry_size;
	}

	return 0;
}
#endif

static int __init do_name(struct unpack_desc *unpack_desc)
{
	umode_t mode = unpack_desc->cpio_header.mode;
	uid_t uid = unpack_desc->cpio_header.uid;
	gid_t gid = unpack_desc->cpio_header.gid;
	unsigned long body_len = unpack_desc->cpio_header.body_len;
	time64_t mtime = unpack_desc->cpio_header.mtime;
	unsigned int rdev = unpack_desc->cpio_header.rdev;
	char *collected = unpack_desc->collected;

	unpack_desc->state = SkipIt;
	unpack_desc->next_state = Reset;

	if (strcmp(collected, "TRAILER!!!") == 0) {
		free_hash(unpack_desc->head);
		return 0;
#ifdef CONFIG_IMA_DIGEST_LIST
	} else if (strcmp(unpack_desc->collected, METADATA_FILENAME) == 0) {
		unpack_desc->metadata.is_metadata = 1;
		unpack_desc->metadata.max_len = body_len;
	} else {
		strcpy(unpack_desc->previous_name_buf, collected);
#endif
	}
	clean_path(collected, mode);
	if (S_ISREG(mode)) {
		int ml = maybe_link(unpack_desc->head, &unpack_desc->cpio_header, collected);

		if (ml >= 0) {
			int openflags = O_WRONLY|O_CREAT;

			if (ml != 1)
				openflags |= O_TRUNC;
			unpack_desc->wfile = filp_open(collected, openflags, mode);
			if (IS_ERR(unpack_desc->wfile))
				return 0;
			unpack_desc->wfile_pos = 0;

			vfs_fchown(unpack_desc->wfile, uid, gid);
			vfs_fchmod(unpack_desc->wfile, mode);
			if (body_len)
				vfs_truncate(&(unpack_desc->wfile->f_path), (loff_t)body_len);
			unpack_desc->state = CopyFile;
		}
	} else if (S_ISDIR(mode)) {
		init_mkdir(collected, mode);
		init_chown(collected, uid, gid, 0);
		init_chmod(collected, mode);
		dir_add(&unpack_desc->dir_list, collected, mtime);
	} else if (S_ISBLK(mode) || S_ISCHR(mode) ||
		   S_ISFIFO(mode) || S_ISSOCK(mode)) {
		if (maybe_link(unpack_desc->head, &unpack_desc->cpio_header, collected) == 0) {
			init_mknod(collected, mode, rdev);
			init_chown(collected, uid, gid, 0);
			init_chmod(collected, mode);
			do_utime(collected, mtime);
		}
	}
	return 0;
}

#ifdef CONFIG_IMA_DIGEST_LIST
static int __init do_process_metadata(struct metadata *metadata, char *pathname, char *buf, int len, bool last)
{
	int ret = 0;
	if (!metadata->metadata_buf) {
		metadata->metadata_buf_ptr = metadata->metadata_buf = kmalloc(metadata->max_len, GFP_KERNEL);
		if (!metadata->metadata_buf_ptr) {
			ret = -ENOMEM;
			goto out;
		}

		metadata->metadata_len = metadata->max_len;
	}

	if (metadata->metadata_buf_ptr + len > metadata->metadata_buf + metadata->metadata_len) {
		ret = -EINVAL;
		goto out;
	}

	memcpy(metadata->metadata_buf_ptr, buf, (__kernel_size_t)len);
	metadata->metadata_buf_ptr += len;

	if (last)
		do_parse_metadata(pathname, metadata->metadata_buf, metadata->metadata_len);
out:
	if (ret < 0 || last) {
		kfree(metadata->metadata_buf);
		metadata->metadata_buf = NULL;
		metadata->is_metadata = 0;
	}

	return ret;
}
#endif

static int __init do_copy(struct unpack_desc *unpack_desc)
{
	unsigned long body_len = unpack_desc->cpio_header.body_len;
	time64_t mtime = unpack_desc->cpio_header.mtime;
	unsigned long byte_count = unpack_desc->byte_count;
	char *victim = unpack_desc->victim;
	struct file *wfile = unpack_desc->wfile;

	if (byte_count >= body_len) {
		struct timespec64 t[2] = { };

		if (xwrite(wfile, victim, body_len, &unpack_desc->wfile_pos) != body_len)
			error("write error");
#ifdef CONFIG_IMA_DIGEST_LIST
		if (unpack_desc->metadata.is_metadata)
			do_process_metadata(&unpack_desc->metadata, unpack_desc->previous_name_buf, victim, (int)body_len, true);
#endif
		t[0].tv_sec = mtime;
		t[1].tv_sec = mtime;
		vfs_utimes(&wfile->f_path, t);

		fput(wfile);
		eat(unpack_desc, body_len);
		unpack_desc->state = SkipIt;
		return 0;
	}

	if (xwrite(wfile, victim, byte_count, &unpack_desc->wfile_pos) != byte_count)
		error("write error");
#ifdef CONFIG_IMA_DIGEST_LIST
	if (unpack_desc->metadata.is_metadata)
		do_process_metadata(&unpack_desc->metadata, unpack_desc->previous_name_buf, victim, (int)byte_count, false);
#endif
	unpack_desc->cpio_header.body_len -= byte_count;
	eat(unpack_desc, byte_count);
	return 1;
}

static int __init do_symlink(struct unpack_desc *unpack_desc)
{
	unsigned long name_len = unpack_desc->cpio_header.name_len;
	unsigned long body_len = unpack_desc->cpio_header.body_len;
	uid_t uid = unpack_desc->cpio_header.uid;
	gid_t gid = unpack_desc->cpio_header.gid;
	time64_t mtime = unpack_desc->cpio_header.mtime;
	char *collected = unpack_desc->collected;

	collected[N_ALIGN(name_len) + body_len] = '\0';
#ifdef CONFIG_IMA_DIGEST_LIST
	strcpy(unpack_desc->previous_name_buf, collected);
#endif
	clean_path(collected, 0);
	init_symlink(collected + N_ALIGN(name_len), collected);
	init_chown(collected, uid, gid, AT_SYMLINK_NOFOLLOW);
	do_utime(collected, mtime);
	unpack_desc->state = SkipIt;
	unpack_desc->next_state = Reset;
	return 0;
}

static __initdata int (*actions[])(struct unpack_desc *unpack_desc) = {
	[Start]		= do_start,
	[Collect]	= do_collect,
	[GotHeader]	= do_header,
	[SkipIt]	= do_skip,
	[GotName]	= do_name,
	[CopyFile]	= do_copy,
	[GotSymlink]	= do_symlink,
	[Reset]		= do_reset,
};

static long __init write_buffer(struct unpack_desc *unpack_desc, char *buf, unsigned long len)
{
	unpack_desc->byte_count = len;
	unpack_desc->victim = buf;

	while (!actions[unpack_desc->state](unpack_desc))
		;
	return len - unpack_desc->byte_count;
}

static long __init flush_buffer(void *bufv, unsigned long len)
{
	char *buf = (char *) bufv;
	long written;
	long origLen = len;
	struct unpack_desc *unpack_desc = pid_to_desc(current->pid);
	if (unpack_desc->message)
		return -1;
	while ((written = write_buffer(unpack_desc, buf, len)) < len && !unpack_desc->message) {
		char c = buf[written];

		if (c == '0') {
			buf += written;
			len -= written;
			unpack_desc->state = Start;
		} else if (c == 0) {
			buf += written;
			len -= written;
			unpack_desc->state = Reset;
		} else
			error("junk within compressed archive");
	}
	return origLen;
}

#include <linux/decompress/generic.h>

static char * __init unpack_to_rootfs(struct unpack_desc *unpack_desc, char *buf, unsigned long len)
{
	long written;
	decompress_fn decompress;
	const char *compress_name;
	unsigned long my_inptr = 0; /* index of next byte to be processed in inbuf */

	INIT_LIST_HEAD(&unpack_desc->dir_list);
	unpack_desc->header_buf = kmalloc(110, GFP_KERNEL);
	unpack_desc->symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);
	unpack_desc->name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);
#ifdef CONFIG_IMA_DIGEST_LIST
	unpack_desc->previous_name_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1,
				    GFP_KERNEL);

	if (!unpack_desc->header_buf || !unpack_desc->symlink_buf ||
		!unpack_desc->name_buf || !unpack_desc->previous_name_buf)
#else
	if (!unpack_desc->header_buf || !unpack_desc->symlink_buf || !unpack_desc->name_buf)
#endif
		panic("can't allocate buffers");

	unpack_desc->state = Start;
	unpack_desc->this_header = 0;
	unpack_desc->message = NULL;
	while (!unpack_desc->message && len) {
		loff_t saved_offset = unpack_desc->this_header;

		if (*buf == '0' && !(unpack_desc->this_header & 3)) {
			unpack_desc->state = Start;
			written = write_buffer(unpack_desc, buf, len);
			buf += written;
			len -= written;
			continue;
		}
		if (!*buf) {
			buf++;
			len--;
			unpack_desc->this_header++;
			continue;
		}
		unpack_desc->this_header = 0;
		decompress = decompress_method(buf, (long)len, &compress_name);
		pr_debug("Detected %s compressed data\n", compress_name);
		if (decompress) {
			int res = decompress(buf, len, NULL, flush_buffer, NULL,
				   &my_inptr, error);
			if (res)
				error("decompressor failed");
		} else if (compress_name) {
			if (!unpack_desc->message) {
				snprintf(unpack_desc->msg_buf, sizeof(unpack_desc->msg_buf),
					 "compression method %s not configured",
					 compress_name);
				unpack_desc->message = unpack_desc->msg_buf;
			}
		} else
			error("invalid magic at start of compressed archive");
		if (unpack_desc->state != Reset)
			error("junk at the end of compressed archive");
		unpack_desc->this_header = saved_offset + my_inptr;
		buf += my_inptr;
		len -= my_inptr;
	}
	dir_utime(&unpack_desc->dir_list);
#ifdef CONFIG_IMA_DIGEST_LIST
	kfree(unpack_desc->previous_name_buf);
#endif
	kfree(unpack_desc->name_buf);
	kfree(unpack_desc->symlink_buf);
	kfree(unpack_desc->header_buf);
	return unpack_desc->message;
}

#define  CPIO_MAGIC_NUM     0x12345678

bool __init is_multi_rootfs(struct multi_rootfs_header *header)
{
	if (header->magic_num != CPIO_MAGIC_NUM)
		return false;
	if (header->cpio_num == 0)
		return false;
	return true;
}

static int __init unpack_task(void *data)
{
	struct rootfs_info *info = (struct rootfs_info *)data;
	struct unpack_desc *unpack_desc = pid_to_desc(current->pid);

	unpack_to_rootfs(unpack_desc, (char *)(uintptr_t)info->offset, info->len);
	complete(&unpack_desc->rootfs_running);
	do_exit(0);
	return 0;
}

static char * __init slow_unpack(struct multi_rootfs_header *header)
{
	unsigned int i;
	char *err = NULL;
	struct unpack_desc *unpack_desc = kzalloc(sizeof(*unpack_desc), GFP_KERNEL);

	if (!unpack_desc)
		return "can not alloc unpack desc!";
	pid_insert_desc(current->pid, unpack_desc);
	for (i = 0; i < header->cpio_num; i++) {
		header->info[i].offset += (unsigned long)header;
		err = unpack_to_rootfs(unpack_desc, (char *)(uintptr_t)header->info[i].offset, header->info[i].len);
		if (err != NULL)
			break;
	}
	delete_pid_desc(current->pid);
	kfree(unpack_desc);
	return err;
}

static struct task_struct *task[MAX_FILES] __initdata;
static pid_t task_id[MAX_FILES] __initdata;
static void __init free_unpack_desc(unsigned int num)
{
	unsigned int i;
	struct unpack_desc *unpack_desc;

	for (i = 0; i < num; i++) {
		unpack_desc = pid_to_desc(task_id[i]);
		delete_pid_desc(task_id[i]);
		kfree(unpack_desc);
	}
}

static char * __init create_unpack_task(struct multi_rootfs_header *header)
{
	unsigned int i;
	struct unpack_desc *unpack_desc;
	char name[NAME_MAX];

	for (i = 0; i < header->cpio_num; i++) {
		unpack_desc = kzalloc(sizeof(*unpack_desc), GFP_KERNEL);
		if (!unpack_desc) {
			free_unpack_desc(i);
			return "can not alloc unpack desc!";
		}
		init_completion(&unpack_desc->rootfs_running);
		header->info[i].offset += (unsigned long)header;
		snprintf(name, sizeof(name), "unpack_to_rootfs:%u", i);
		task[i] = kthread_create_on_cpu(unpack_task, (void *)&(header->info[i]), i, name);
		if (IS_ERR(task[i])) {
			free_unpack_desc(i);
			return "create unpack task fail!";
		}
		task_id[i] = task[i]->pid;
		pid_insert_desc(task_id[i], unpack_desc);
	}
	return NULL;
}

static void __init wake_up_unpack_task(unsigned int num)
{
	unsigned int i;

	for (i = 0; i < num; i++)
		wake_up_process(task[i]);
}

static char * __init wait_unpack_task_done(struct multi_rootfs_header *header)
{
	unsigned int i;
	struct unpack_desc *unpack_desc;
	char *err = NULL;

	for (i = 0; i < header->cpio_num; i++) {
		unpack_desc = pid_to_desc(task_id[i]);
		wait_for_completion(&unpack_desc->rootfs_running); /* wait_for_completion_timeout; */
		if (!unpack_desc->message)
			err = unpack_desc->message;
		delete_pid_desc(task_id[i]);
		kfree(unpack_desc);
	}
	return err;
}
char * __init multi_threads_unpack_to_rootfs(struct multi_rootfs_header *header)
{
	unsigned int cpus = num_online_cpus();
	char *err;

	pr_info("find %u initrd.cpio files, online cpu is %u\n", header->cpio_num, cpus);
	if (cpus < header->cpio_num)
		return slow_unpack(header);
	err = create_unpack_task(header);
	if (err != NULL)
		return err;
	wake_up_unpack_task(header->cpio_num);
	return wait_unpack_task_done(header);
}
